package jmpo.strm;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Supplier;
import java.util.stream.Stream;

/**
 * Промежуточные операции intermediate operations - это операции, которые из одного потока данных
 * формируют другой поток данных.
 * Stream допускает следующие промежуточные операции:
 *     filter()   - получает на вход Predicate и пропускает только те, на которых предикат даёт true
 *     map()      - получает на вход преобразование, которое берёт элемент из потока и 
 *     обязательно возвращает новый элемент, возможно другого типа
 *     flatMap()  - если поток данных состоит из коллекций данных, а мы хотим получить один поток по всем 
 *     коллекциям, то используется этот метод
 *     distinct() - собирает только различные элементы
 *     sorted()   - сортирует входящие данные либо естественным порядком, либо переданным компаратором
 *     peek()     - принимает операцию, которую нужно совершить над каждым элементом, но не удаляет элемент
 *     из потока данных, как forEach, а передаёт его далее
 *     limit()    - ограничивает поток переданным количеством элементов, входящий параметр имеет тип long
 *     skip()     - пропускает указанное количество элементов, входящий параметр имеет тип long
 */

/**
 * Важно!
 * Промежуточная операция sorted требуют сохранения информации о всех элементах потока!
 * Потому попытка написать вызов .sorted().limit(N) на бесконечном потоке завершится 
 * падением JVM по переполнению памяти.
 */

/**
 * Важно!
 * Операция distinct внутри применяет HashSet. Это накладывает важные ограничения на используемые 
 * данные:
 *     они должны верно перегрузить метод equals
 *     они должны верно перегрузить метод hashCode
 */

/**
 * Важно!
 * Операции filter и distinct устроены таким образом, что при запросе следующего элемента они отбраковывают 
 * те, что не удовлетворяют фильтру или повторяются. Т.е. количество элементов в потоке уменьшается.
 * Это может привести к зависанию бесконечного потока данных!
 * Например, если мы ищем все положительные элементы, но поток генерирует только отрицательные числа, то 
 * filter приведёт к зависанию.
 * Если поток генериует только 5 различных значений, но после distinct у нас стоит limit(6), то программа 
 * зависнет, так как distinct не сможет выдать шестое значение.
 */

/**
 * Методы map и peek пропускают элементы 1 в 1, т.е. на каждый элемент во входящем потоке будет 1 элемент в 
 * выходящем потоке.
 */ 

/** 
 * flatMap поочерёдно обходит каждый получившийся с использованием функции преобразования поток, т.е. 1 элемент
 * может превратиться в несколько.
 */

record Student (String name, long id) { }

final class FilterExample {
    public static void test(List<Student> lst) {
        long cnt = lst.stream().filter(s -> s.name().length() > 4).count();
        System.out.format("%d students with long names\n", cnt);
    }
}

final class MapExample {
    public static void test(List<Student> lst) {
        long cnt = lst.stream().map(s -> s.name()).map(s -> s.length()).filter(l -> l > 4).count();
        System.out.format("%d students with long names\n", cnt);
    }
}

final class FlatMapExample {
    public static void test(String filename) throws IOException {
        try (Stream<String> lines = new BufferedReader(new FileReader(filename)).lines()) {
            long cnt = lines
                .map(l -> l.split(" "))
                .flatMap(wordsArray -> Arrays.stream(wordsArray))
                .map(w -> w.length())
                .filter(l -> l > 5)
                .count();
            System.out.format("%d long words\n", cnt); 
        }
    }
}

final class PeekExample {
    public static void test(String filename) throws IOException {
        try (Stream<String> lines = new BufferedReader(new FileReader(filename)).lines()) {
            long cnt = lines
                .map(l -> l.split(" "))
                .flatMap(wordsArray -> Arrays.stream(wordsArray))
                .map(w -> w.length())
                .filter(l -> l > 5)
                .peek(System.out::println)
                .count();
            System.out.format("%d long words\n", cnt); 
        }
    } 
}

final class DistinctExample {
    public static void test(long cnt) {
        Stream<Integer> intstream = Stream.generate(() ->ThreadLocalRandom.current().nextInt());
        intstream
            .filter(i -> i > 0)
            .distinct()
            .limit(cnt)
            .forEach(System.out::println);
    }
}

final class SortedExample {
    public static void test(long cnt) {
        Stream<Integer> intstream = Stream.generate(() ->ThreadLocalRandom.current().nextInt());
        intstream
            .filter(i -> i > 0)
            .distinct()
            .limit(cnt)
            .sorted()
            .forEach(System.out::println);
    }
}

final class ComparatorSortedExample {
    public static void test(List<Student> lst) {
        lst.stream()
            .sorted((s1, s2) -> s1.name().compareTo(s2.name()))
            .map(s -> s.name())
            .forEach(System.out::println);
    }
}

final class LastCountExample {
    public static void test(long cnt) {
        Stream<Integer> intstream = Stream.generate(() ->ThreadLocalRandom.current().nextInt());
        intstream
            .filter(i -> i > 0)
            .distinct()
            .limit(cnt)
            .sorted()
            .skip(cnt/2)
            .forEach(System.out::println);
    }
}

public class IntermediateOperation {
    public static void main(String[] args) throws IOException {
        List<Student> lst = List.of(
            new Student("Ann", 1),
            new Student("Bob", 10),
            new Student("Alice", 12),
            new Student("Sam", 3),
            new Student("Carol", 8),
            new Student("Dave", 7),
            new Student("Eve", 12)
        );
        if (false) FilterExample.test(lst);
        if (false) MapExample.test(lst);
        if (false) FlatMapExample.test(args[0]);
        if (false) PeekExample.test(args[0]);
        if (false) DistinctExample.test(20);
        if (false) SortedExample.test(5);
        if (false) ComparatorSortedExample.test(lst);
        if (false) LastCountExample.test(20);
    }
}
